/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.compose.animation.core

import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue

/**
 * [AnimationState] contains the necessary information to indicate the state of an animation. Once
 * an [AnimationState] is constructed, it can only be updated/mutated by animations. If there's a
 * need to mutate some of the fields of an [AnimationState], consider using [copy] functions.
 *
 * @param typeConverter [TwoWayConverter] to convert type [T] from and to [AnimationVector]
 * @param initialValue initial value of the [AnimationState]
 * @param initialVelocityVector initial velocity of the [AnimationState], null (i.e. no velocity) by
 *   default.
 * @param lastFrameTimeNanos last frame time of the animation, [AnimationConstants.UnspecifiedTime]
 *   by default
 * @param finishedTimeNanos the time that the animation finished successfully,
 *   [AnimationConstants.UnspecifiedTime] until then
 * @param isRunning whether the [AnimationState] is currently being updated by an animation. False
 *   by default
 */
public class AnimationState<T, V : AnimationVector>(
    public val typeConverter: TwoWayConverter<T, V>,
    initialValue: T,
    initialVelocityVector: V? = null,
    lastFrameTimeNanos: Long = AnimationConstants.UnspecifiedTime,
    finishedTimeNanos: Long = AnimationConstants.UnspecifiedTime,
    isRunning: Boolean = false,
) : State<T> {
    /** Current value of the [AnimationState]. */
    override var value: T by mutableStateOf(initialValue)
        internal set

    /** Current velocity vector of the [AnimationState]. */
    public var velocityVector: V =
        initialVelocityVector?.copy() ?: typeConverter.createZeroVectorFrom(initialValue)
        internal set

    /**
     * Last frame time of the animation.
     *
     * If the animation has never started, this will be [AnimationConstants.UnspecifiedTime], unless
     * specified otherwise in the [AnimationState] constructor. [lastFrameTimeNanos] is the frame
     * time when the animation is last updated, in the [System.nanoTime] timebase. It is also used
     * for starting a sequential animation in [AnimationState.animateTo]. This allows the sequential
     * animation to set its start time to when the previous animation is interrupted or finished.
     */
    @get:Suppress("MethodNameUnits")
    public var lastFrameTimeNanos: Long = lastFrameTimeNanos
        internal set

    /**
     * The time when the animation finished successfully in the [System.nanoTime] timebase.
     *
     * If the animation has never finished (i.e. currently running, interrupted, or never started),
     * this will be [AnimationConstants.UnspecifiedTime], unless specified otherwise in
     * [AnimationState] constructor.
     */
    @get:Suppress("MethodNameUnits")
    public var finishedTimeNanos: Long = finishedTimeNanos
        internal set

    /** Indicates whether the animation is currently running. */
    public var isRunning: Boolean = isRunning
        internal set

    /** Velocity of type [T], converted from [velocityVector]. */
    public val velocity: T
        get() = typeConverter.convertFromVector(velocityVector)

    override fun toString(): String {
        return "AnimationState(" +
            "value=$value, " +
            "velocity=$velocity, " +
            "isRunning=$isRunning, " +
            "lastFrameTimeNanos=$lastFrameTimeNanos, " +
            "finishedTimeNanos=$finishedTimeNanos" +
            ")"
    }
}

/**
 * Indicates whether the given [AnimationState] is for an animation that has finished, indicated by
 * [AnimationState.finishedTimeNanos] having a specified value.
 */
public val AnimationState<*, *>.isFinished: Boolean
    get() = finishedTimeNanos != AnimationConstants.UnspecifiedTime

/**
 * [AnimationScope] provides all the animation related info specific to an animation run. An
 * [AnimationScope] will be accessible during an animation.
 *
 * @see [AnimationState.animateTo]
 */
public class AnimationScope<T, V : AnimationVector>
internal constructor(
    initialValue: T,
    /** [TwoWayConverter] to convert type [T] from and to [AnimationVector]. */
    public val typeConverter: TwoWayConverter<T, V>,
    initialVelocityVector: V,
    lastFrameTimeNanos: Long,
    /** Target value of the animation. */
    public val targetValue: T,
    /** Start time of the animation in the [System.nanoTime] timebase. */
    @get:Suppress("MethodNameUnits") public val startTimeNanos: Long,
    isRunning: Boolean,
    private val onCancel: () -> Unit,
) {
    // Externally immutable fields
    /** Current value of the [AnimationScope]. */
    public var value: T by mutableStateOf(initialValue)
        internal set

    /** Current velocity vector of the [AnimationScope]. */
    public var velocityVector: V = initialVelocityVector.copy()
        internal set

    /**
     * Last frame time of the animation.
     *
     * If the animation has never started, this will be [AnimationConstants.UnspecifiedTime], unless
     * specified otherwise in the [AnimationState] constructor. [lastFrameTimeNanos] is the frame
     * time when the animation is last updated, in the [System.nanoTime] timebase. It is also used
     * for starting a sequential animation in [AnimationState.animateTo]. This allows the sequential
     * animation to set its start time to when the previous animation is interrupted or finished.
     */
    @get:Suppress("MethodNameUnits")
    public var lastFrameTimeNanos: Long = lastFrameTimeNanos
        internal set

    /**
     * The time when the animation finished successfully in the [System.nanoTime] timebase.
     *
     * If the animation has never finished (i.e. currently running, interrupted, or never started),
     * this will be [AnimationConstants.UnspecifiedTime], unless specified otherwise in
     * [AnimationState] constructor.
     */
    @get:Suppress("MethodNameUnits")
    public var finishedTimeNanos: Long = AnimationConstants.UnspecifiedTime
        internal set

    /** Indicates whether the animation is currently running. */
    public var isRunning: Boolean by mutableStateOf(isRunning)
        internal set

    /** Velocity of type [T], converted from [velocityVector]. */
    public val velocity: T
        get() = typeConverter.convertFromVector(velocityVector)

    /**
     * Cancels the animation that this [AnimationScope] corresponds to. The scope will not be
     * updated any more after [cancelAnimation] is called.
     */
    public fun cancelAnimation() {
        isRunning = false
        onCancel()
    }

    /**
     * Creates an [AnimationState] that populates all the fields in [AnimationState] from
     * [AnimationScope].
     */
    public fun toAnimationState(): AnimationState<T, V> =
        AnimationState(
            typeConverter,
            value,
            velocityVector,
            lastFrameTimeNanos,
            finishedTimeNanos,
            isRunning,
        )
}

/**
 * Creates a new [AnimationState] from a given [AnimationState]. This function allows some of the
 * fields to be different in the new [AnimationState].
 *
 * @param value value of the [AnimationState], using the value of the given [AnimationState] by
 *   default
 * @param velocityVector velocity of the [AnimationState], using the velocity of the given
 *   [AnimationState] by default.
 * @param lastFrameTimeNanos last frame time of the animation, same as the given [AnimationState] by
 *   default
 * @param finishedTimeNanos the time that the animation finished successfully,
 *   [AnimationConstants.UnspecifiedTime] until then. Default value is the same as the given
 *   [AnimationState].
 * @param isRunning whether the [AnimationState] is currently being updated by an animation. Same as
 *   the given [AnimationState] by default
 * @return A new [AnimationState] instance copied from the given instance, with some fields
 *   optionally altered
 */
public fun <T, V : AnimationVector> AnimationState<T, V>.copy(
    value: T = this.value,
    velocityVector: V? = this.velocityVector.copy(),
    lastFrameTimeNanos: Long = this.lastFrameTimeNanos,
    finishedTimeNanos: Long = this.finishedTimeNanos,
    isRunning: Boolean = this.isRunning,
): AnimationState<T, V> =
    AnimationState(
        this.typeConverter,
        value,
        velocityVector,
        lastFrameTimeNanos,
        finishedTimeNanos,
        isRunning,
    )

/**
 * Creates a new [AnimationState] of Float [value] type from a given [AnimationState] of the same
 * type. This function allows some of the fields to be different in the new [AnimationState].
 *
 * @param value value of the [AnimationState], using the value of the given [AnimationState] by
 *   default
 * @param velocity velocity of the [AnimationState], using the velocity of the given
 *   [AnimationState] by default.
 * @param lastFrameTimeNanos last frame time of the animation, same as the given [AnimationState] by
 *   default
 * @param finishedTimeNanos the time that the animation finished successfully, same as the given
 *   [AnimationState] by default.
 * @param isRunning whether the [AnimationState] is currently being updated by an animation. Same as
 *   the given [AnimationState] by default
 * @return A new [AnimationState] instance copied from the given instance, with some fields
 *   optionally altered
 */
public fun AnimationState<Float, AnimationVector1D>.copy(
    value: Float = this.value,
    velocity: Float = this.velocityVector.value,
    lastFrameTimeNanos: Long = this.lastFrameTimeNanos,
    finishedTimeNanos: Long = this.finishedTimeNanos,
    isRunning: Boolean = this.isRunning,
): AnimationState<Float, AnimationVector1D> =
    AnimationState(
        this.typeConverter,
        value,
        AnimationVector(velocity),
        lastFrameTimeNanos,
        finishedTimeNanos,
        isRunning,
    )

/**
 * Factory method for creating an [AnimationState] for Float [initialValue].
 *
 * @param initialValue initial value of the [AnimationState]
 * @param initialVelocity initial velocity of the [AnimationState], 0 (i.e. no velocity) by default
 * @param lastFrameTimeNanos last frame time of the animation, [AnimationConstants.UnspecifiedTime]
 *   by default
 * @param finishedTimeNanos the time that the animation finished successfully,
 *   [AnimationConstants.UnspecifiedTime] by default.
 * @param isRunning whether the [AnimationState] is currently being updated by an animation. False
 *   by default
 * @return A new [AnimationState] instance
 */
public fun AnimationState(
    initialValue: Float,
    initialVelocity: Float = 0f,
    lastFrameTimeNanos: Long = AnimationConstants.UnspecifiedTime,
    finishedTimeNanos: Long = AnimationConstants.UnspecifiedTime,
    isRunning: Boolean = false,
): AnimationState<Float, AnimationVector1D> {
    return AnimationState(
        Float.VectorConverter,
        initialValue,
        AnimationVector(initialVelocity),
        lastFrameTimeNanos,
        finishedTimeNanos,
        isRunning,
    )
}

/**
 * Factory method for creating an [AnimationState] with an [initialValue] and an [initialVelocity].
 *
 * @param typeConverter [TwoWayConverter] to convert type [T] from and to [AnimationVector]
 * @param initialValue initial value of the [AnimationState]
 * @param initialVelocity initial velocity of the [AnimationState]
 * @param lastFrameTimeNanos last frame time of the animation, [AnimationConstants.UnspecifiedTime]
 *   by default
 * @param finishedTimeNanos the time that the animation finished successfully,
 *   [AnimationConstants.UnspecifiedTime] by default.
 * @param isRunning whether the [AnimationState] is currently being updated by an animation. False
 *   by default
 * @return A new [AnimationState] instance
 */
public fun <T, V : AnimationVector> AnimationState(
    typeConverter: TwoWayConverter<T, V>,
    initialValue: T,
    initialVelocity: T,
    lastFrameTimeNanos: Long = AnimationConstants.UnspecifiedTime,
    finishedTimeNanos: Long = AnimationConstants.UnspecifiedTime,
    isRunning: Boolean = false,
): AnimationState<T, V> {
    return AnimationState(
        typeConverter,
        initialValue,
        typeConverter.convertToVector(initialVelocity),
        lastFrameTimeNanos,
        finishedTimeNanos,
        isRunning,
    )
}

/**
 * Creates an AnimationVector with all the values set to 0 using the provided [TwoWayConverter] and
 * the [value].
 *
 * @return a new AnimationVector instance of type [V].
 */
public fun <T, V : AnimationVector> TwoWayConverter<T, V>.createZeroVectorFrom(value: T): V =
    convertToVector(value).also { it.reset() }
